﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Common.Utilities;
using Common.Utilities.TextMatch;

namespace Workflows.Components.ETL
{
    /// <summary>
    /// 
    /// </summary>
    public enum CitationField
    {
        /// <summary></summary>
        Unknown = 0,
        /// <summary></summary>
        AuthorList = 1,
        /// <summary></summary>
        Title = 2,
        /// <summary></summary>
        JournalMeetingBookSiteRefName = 3,
        /// <summary></summary>
        VolumeIssuePage = 4,
        /// <summary></summary>
        VolumeIssue = 5,
        /// <summary></summary>
        Volume = 6,
        /// <summary></summary>
        Issue = 7,
        /// <summary></summary>
        Pages = 8,
        /// <summary></summary>
        Url = 9,
        /// <summary></summary>
        PubYear = 10,
        /// <summary></summary>
        Date = 11,
        /// <summary></summary>
        Location = 12
    }

    /// <summary>
    /// 1. author list
    /// 2. title
    /// 3. journal name, meeting name, book name, web site, reference name
    /// 4. volume and issue
    /// 5. page ranges
    /// 6. url
    /// 7. pub year
    /// 8. date
    /// 9. location
    /// 
    /// split order is:
    /// 1. pub year
    /// 2. page key -> page ranges
    /// 3. numbers, volume, issues
    /// 4. date
    /// 5. url
    /// 6. authors
    /// 7. title 
    /// 8. journal name (closer to volume/issue)
    /// 9. location 
    /// </summary>
    public class CitationStyleEncoding
    {
        #region props
        private Dictionary<char, string> _Encodings;
        ///<summary></summary>
        public Dictionary<char, string> Encodings
        {
            get
            {
                return _Encodings;
            }
            set
            {
                _Encodings = value;
            }
        }

        private Dictionary<char, List<char>> _StopWordsEncodings;
        ///<summary></summary>
        public Dictionary<char, List<char>> StopWordsEncodings
        {
            get
            {
                return _StopWordsEncodings;
            }
            set
            {
                _StopWordsEncodings = value;
            }
        }
        
        private string _JournalNamePattern;
        /// <summary>
        /// journal name can only contain the following characters
        /// [a-zA-Z\.\s]
        /// </summary>
        public string JournalNamePattern
        {
            get
            {
                return this._JournalNamePattern;
            }
            set
            {
                this._JournalNamePattern = value;
            }
        }
        
        private string _AuthorListPattern;
        /// <summary>
        /// multiple authors are separated by ","
        /// author(s) field should end with "."
        /// special separators include "&", "and"
        /// after splitting authors, each author name should be less than 5 words
        /// author(s) can only have the following characters:
        /// [a-zA-Z\-\.'\s,]
        /// </summary>
        public string AuthorListPattern
        {
            get
            {
                return this._AuthorListPattern;
            }
            set
            {
                this._AuthorListPattern = value;
            }
        }
        
        private string _PubYearPattern;
        /// <summary>
        /// \b(19|20)\d\d\b
        /// </summary>
        public string PubYearPattern
        {
            get
            {
                return this._PubYearPattern;
            }
            set
            {
                this._PubYearPattern = value;
            }
        }

        private string _VolumeIssuePagePattern;
        /// <summary>
        /// 12:100-103
        /// 12(2) 99-103
        /// </summary>
        public string VolumeIssuePagePattern
        {
            get
            {
                return this._VolumeIssuePagePattern;
            }
            set
            {
                this._VolumeIssuePagePattern = value;
            }
        }

        private string _PageKeyPattern;
        /// <summary></summary>
        public string PageKeyPattern
        {
            get
            {
                return this._PageKeyPattern;
            }
            set
            {
                this._PageKeyPattern = value;
            }
        }
        
        private string _PageRangePattern;
        /// <summary>
        /// key: page, pp, p
        /// (\d+)\s*[-~]\s*(\d+)
        /// </summary>
        public string PageRangePattern
        {
            get
            {
                return this._PageRangePattern;
            }
            set
            {
                this._PageRangePattern = value;
            }
        }
        
        private string _VolumeIssuePattern;
        /// <summary>
        /// \d+(\d+)
        /// \d+\s+\d+
        /// \d+\.\d+
        /// </summary>
        public string VolumeIssuePattern
        {
            get
            {
                return this._VolumeIssuePattern;
            }
            set
            {
                this._VolumeIssuePattern = value;
            }
        }

        private string _VolumePattern;
        /// <summary>
        /// vol[\.]|volume\s+(\d+)
        /// </summary>
        public string VolumePattern
        {
            get
            {
                return this._VolumePattern;
            }
            set
            {
                this._VolumePattern = value;
            }
        }

        private string _IssuePattern;
        /// <summary>
        /// issue|no\.|number\s+(\d+)
        /// </summary>
        public string IssuePattern
        {
            get
            {
                return this._IssuePattern;
            }
            set
            {
                this._IssuePattern = value;
            }
        }
        
        private string _DatePattern;
        /// <summary>
        /// mm-dd-yy or mm-dd-yyyy or mm/dd/yy or mm/dd/yyyy
        /// MMM dd yyyy or MMM dd, yyyy or MMM dd yy or MMM dd, yy
        /// Quarter yyyy or Quarter yy
        /// MMM yy or MMM yyyy
        /// </summary>
        public string DatePattern
        {
            get
            {
                return this._DatePattern;
            }
            set
            {
                this._DatePattern = value;
            }
        }

        private string _UrlPattern;
        /// <summary></summary>
        public string UrlPattern
        {
            get
            {
                return this._UrlPattern;
            }
            set
            {
                this._UrlPattern = value;
            }
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        public CitationStyleEncoding()
        {
            this.PopulateCitationFieldEncodings();
            this.PopulateStopWords();

            this._JournalNamePattern = "^[a-zA-Z\\.\\s]+$";
            this._AuthorListPattern = "^[a-zA-Z\\-\\.'\\s,]+$";
            this._PubYearPattern = "\\b(19|20)\\d\\d\\b";
            this._VolumeIssuePagePattern =
                "(?<v>\\d+)\\s*:\\s*(?<p1>\\d+)\\s*\\-\\s*(?<p2>\\d+)|"+
                "(?<v>\\d+)\\s*\\(\\s*(?<i>\\d+[\\-\\s]*\\d*)\\s*\\)\\s*:?\\s*(?<p1>\\d+)\\s*\\-\\s*(?<p2>\\d+)|" +
                "(?<v>\\d+)\\s*:\\s*[p|pp|page]\\.?\\s*(?<p1>\\d+)\\s*[\\-~]\\s*(?<p2>\\d+)";
            this._PageKeyPattern =
                "\\b(page|pp|p)[\\s\\.]+(?<p1>\\d+)([\\-\\s,&]+(?<p2>\\d+))*|" +
                "\\b(?<p1>\\d+)\\s*[\\-~]?\\s*(?<p2>\\d+)?\\b\\s*p\\.";
            this._PageRangePattern = "(\\d+)\\s*[\\-~]+\\s*(\\d+)";
            this._VolumeIssuePattern = 
                "(?<v>\\d+)\\s*\\(\\s*(?<i>[A|S]?\\d+[\\-\\s]*\\d*)\\s*\\)|"+
                "(?<v>\\d+)\\s+(?<i>[A|S]?\\d+)|" +
                "(?<v>\\d+)\\.(?<i>[A|S]?\\d+)|" +
                "(?<v>\\d+)\\s*:\\s*(?<i>[A|S]?\\d+)";
            this._VolumePattern =
                "\\bvol\\.\\s+(?<v>\\d+)|" +
                "\\bvol\\s+(?<v>\\d+)|" +
                "\\bv\\.\\s+(?<v>\\d+)|" +
                "\\bv\\s+(?<v>\\d+)|" +
                "\\bvolume\\s+(?<v>\\d+)";
            this._IssuePattern =
                "\\bissue\\s+(?<i>\\d+)|" +
                "\\bno\\.\\s+(?<i>\\d+)|" +
                "\\bno\\s+(?<i>\\d+)|" +
                "\\bnumber\\s+(?<i>\\d+)";
            this._DatePattern = "(?<m>Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|June|Jul|July|Aug|August|Sep|Sept|September|Oct|October|Nov|November|Dec|December)[\\s\\.\\-,]+(?<d>\\d+\\s*[\\-~]*\\s*\\d*)?[,\\-\\s]*(?<y>(19|20)\\d\\d)";
            this._UrlPattern = RegexPatterns.URL;
        }

        #region init
        private void PopulateCitationFieldEncodings()
        {
            // the most common fields in citation strings are:
            // title, journal, author, volume, number, page, issue,
            // month and year
            this.Encodings = new Dictionary<char, string>();
            this.Encodings.Add('A', "Author");
            this.Encodings.Add('T', "Title");
            this.Encodings.Add('L', "Journal");
            this.Encodings.Add('F', "Volume value");
            this.Encodings.Add('W', "Issue value");
            this.Encodings.Add('H', "Page value");
            // unrecognized token
            this.Encodings.Add('X', "noise");
            this.Encodings.Add('M', "Month");
            this.Encodings.Add('Y', "Year");
            this.Encodings.Add('S', "Issue key");
            this.Encodings.Add('P', "Page key");
            this.Encodings.Add('V', "volume key");
            this.Encodings.Add('N', "Numeral");
            // more than one white space
            this.Encodings.Add('B', "blank");
        }

        private void PopulateStopWords()
        {
            this.StopWordsEncodings = new Dictionary<char, List<char>>();
            // punctuation marks such as '-','!','?' do not separate fields, 
            // we only use one symbol to represent all of them
            this.StopWordsEncodings.Add(
                'Q',
                new List<char>() { '@', '#', '$', '%', '^', '&', '*', '+', '=', '\\', '|', '~', '_', ',', '/', '!', '?', ':' });
            // brackets anre parentheses are synonymous, so we use one symbol to represent them
            this.StopWordsEncodings.Add('I', new List<char>() { '(', '[', '{' });
            this.StopWordsEncodings.Add('K', new List<char>() { ')', ']', '}' });
            // the punctuation marks normally used to separate fields are:
            // ,.;:', here we assign each punctuation mark a symbol to 
            // represent it
            this.StopWordsEncodings.Add('D', new List<char>() { '.' });
            this.StopWordsEncodings.Add('G', new List<char>() { '"' });
            this.StopWordsEncodings.Add('R', new List<char>() { ',' });
            this.StopWordsEncodings.Add('C', new List<char>() { '-' });
            this.StopWordsEncodings.Add('E', new List<char>() { '\'', '`' });
            this.StopWordsEncodings.Add('Z', new List<char>() { ';' });
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="citationText"></param>
        /// <returns></returns>
        public bool ContainsMultipleCitations(string citationText)
        {
            throw new NotImplementedException("ContainsMultipleCitations not implemented");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="citationText"></param>
        /// <returns></returns>
        public Citation Parse(string citationText)
        {
            Citation cite=new Citation();
            Dictionary<Range<int>, int> pubYears;
            Dictionary<Range<int>, Range<int>> pages;
            Dictionary<Range<int>, string> volumeIssues;
            Dictionary<Range<int>, int> numbers;
            Dictionary<Range<int>, string> dates;
            Dictionary<Range<int>, string> urls;
            Dictionary<CitationField, List<Range<int>>> encodings = new Dictionary<CitationField, List<Range<int>>>();
            Range<int> originalRange=new Range<int>(0,citationText.Length);
            List<Range<int>> ranges=new List<Range<int>>();
            ranges.Add(originalRange);
            encodings.Add(CitationField.Unknown, ranges);

            // 1a. Date -> pub year
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                List<Range<int>> dateRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                        unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    if (this.TryParseDates(text, out dates))
                    {
                        foreach (Range<int> range in dates.Keys)
                        {
                            Range<int> pubTimeRange = new Range<int>(
                                range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                            dateRanges.Add(pubTimeRange);
                            string date = dates[range];
                            if (string.IsNullOrEmpty(cite.PubTime))
                                cite.PubTime = date;
                        }
                    }

                    List<Range<int>> remains = this.Remove(unknownRange, dateRanges);
                    rangesLeft.AddRange(remains);
                }
                encodings.Add(CitationField.PubYear, dateRanges);
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 1b. pub year 
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                List<Range<int>> yearRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                        unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    if (this.TryParseYear(text, out pubYears))
                    {
                        foreach (Range<int> range in pubYears.Keys)
                        {
                            Range<int> yearRange = new Range<int>(
                                range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                            yearRanges.Add(yearRange);
                            cite.PubTime = pubYears[range].ToString();
                        }
                    }

                    List<Range<int>> remains = this.Remove(unknownRange, yearRanges);
                    rangesLeft.AddRange(remains);
                }
                if (!encodings.ContainsKey(CitationField.PubYear))
                    encodings.Add(CitationField.PubYear, yearRanges);
                else if (encodings[CitationField.PubYear].Count == 0)
                    encodings[CitationField.PubYear] = yearRanges;
                
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 2a. vol-issue-page
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                List<Range<int>> vipRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                Dictionary<Range<int>, string> vips;
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                        unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    if (this.TryParseVolumeIssuePage(text, out vips))
                    {
                        foreach (Range<int> range in vips.Keys)
                        {
                            Range<int> vipRange = new Range<int>(
                                range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                            vipRanges.Add(vipRange);
                            Regex vipRegex=new Regex(this._VolumeIssuePagePattern);
                            Match vipMatch = vipRegex.Match(vips[range]);
                            if(vipMatch.Success)
                            {
                                cite.Volume = vipMatch.Groups["v"].Value;
                                cite.Issue = vipMatch.Groups["i"].Value;
                                cite.Pages = string.Format(
                                    "{0}-{1}", vipMatch.Groups["p1"].Value, vipMatch.Groups["p2"].Value);
                            }
                        }
                    }

                    List<Range<int>> remains = this.Remove(unknownRange, vipRanges);
                    rangesLeft.AddRange(remains);
                }
                encodings.Add(CitationField.VolumeIssuePage, vipRanges);
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 2b. page 
            if (string.IsNullOrEmpty(cite.Pages))
            {
                if (encodings.ContainsKey(CitationField.Unknown))
                {
                    List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                    List<Range<int>> pageRanges = new List<Range<int>>();
                    List<Range<int>> rangesLeft = new List<Range<int>>();
                    foreach (Range<int> unknownRange in unknownRanges)
                    {
                        string text = citationText.Substring(
                            unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                        if (this.TryParsePageRanges(text, out pages))
                        {
                            foreach (Range<int> range in pages.Keys)
                            {
                                Range<int> pageRange = new Range<int>(
                                    range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                                pageRanges.Add(pageRange);
                                cite.Pages = pages[range].ToString();
                            }
                        }

                        List<Range<int>> remains = this.Remove(unknownRange, pageRanges);
                        rangesLeft.AddRange(remains);
                    }
                    encodings.Add(CitationField.Pages, pageRanges);
                    encodings[CitationField.Unknown] = rangesLeft;
                }
            }

            // 2c. volume+issues 
            if (string.IsNullOrEmpty(cite.Volume))
            {
                if (encodings.ContainsKey(CitationField.Unknown))
                {
                    List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                    List<Range<int>> volIssueRanges = new List<Range<int>>();
                    List<Range<int>> rangesLeft = new List<Range<int>>();
                    foreach (Range<int> unknownRange in unknownRanges)
                    {
                        string text = citationText.Substring(
                            unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                        if (this.TryParseVolumeIssues(text, out volumeIssues))
                        {
                            foreach (Range<int> range in volumeIssues.Keys)
                            {
                                Range<int> volIssueRange = new Range<int>(
                                    range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                                volIssueRanges.Add(volIssueRange);
                                string volIssue = volumeIssues[range];
                                Regex viRegex = new Regex(this._VolumeIssuePattern);
                                Match viMatch = viRegex.Match(volIssue);
                                if (viMatch.Success)
                                {
                                    cite.Volume = viMatch.Groups["v"].Value;
                                    cite.Issue = viMatch.Groups["i"].Value;
                                }
                                else
                                {
                                    cite.Volume = volIssue;
                                }
                            }
                        }

                        List<Range<int>> remains = this.Remove(unknownRange, volIssueRanges);
                        rangesLeft.AddRange(remains);
                    }
                    encodings.Add(CitationField.VolumeIssue, volIssueRanges);
                    encodings[CitationField.Unknown] = rangesLeft;
                }
            }

            // 2d. Volume
            if (string.IsNullOrEmpty(cite.Volume))
            {
                if (encodings.ContainsKey(CitationField.Unknown))
                {
                    List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                    List<Range<int>> volumnRanges = new List<Range<int>>();
                    List<Range<int>> rangesLeft = new List<Range<int>>();
                    foreach (Range<int> unknownRange in unknownRanges)
                    {
                        Dictionary<Range<int>, int> volumes;
                        string text = citationText.Substring(
                            unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                        if (this.TryParseVolume(text, out volumes))
                        {
                            foreach (Range<int> range in volumes.Keys)
                            {
                                Range<int> volIssueRange = new Range<int>(
                                    range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                                volumnRanges.Add(volIssueRange);
                                int vol = volumes[range];
                                cite.Volume = vol.ToString();
                            }
                        }

                        List<Range<int>> remains = this.Remove(unknownRange, volumnRanges);
                        rangesLeft.AddRange(remains);
                    }
                    encodings.Add(CitationField.Volume, volumnRanges);
                    encodings[CitationField.Unknown] = rangesLeft;
                }
            }

            // 2e. issue
            if (string.IsNullOrEmpty(cite.Issue))
            {
                if (encodings.ContainsKey(CitationField.Unknown))
                {
                    List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                    List<Range<int>> issueRanges = new List<Range<int>>();
                    List<Range<int>> rangesLeft = new List<Range<int>>();
                    foreach (Range<int> unknownRange in unknownRanges)
                    {
                        Dictionary<Range<int>, int> issues;
                        string text = citationText.Substring(
                            unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                        if (this.TryParseIssue(text, out issues))
                        {
                            foreach (Range<int> range in issues.Keys)
                            {
                                Range<int> issueRange = new Range<int>(
                                    range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                                issueRanges.Add(issueRange);
                                int issue = issues[range];
                                cite.Issue = issue.ToString();
                            }
                        }

                        List<Range<int>> remains = this.Remove(unknownRange, issueRanges);
                        rangesLeft.AddRange(remains);
                    }
                    encodings.Add(CitationField.Issue, issueRanges);
                    encodings[CitationField.Unknown] = rangesLeft;
                }
            }

            // 3. URL 
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                List<Range<int>> urlRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                        unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    if (this.TryParseUrls(text, out urls))
                    {
                        foreach (Range<int> range in urls.Keys)
                        {
                            Range<int> urlRange = new Range<int>(
                                range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                            urlRanges.Add(urlRange);
                            string url = urls[range];
                            if (string.IsNullOrEmpty(cite.PubTime))
                                cite.BookTitleOrMeetingName = url;
                        }
                    }

                    List<Range<int>> remains = this.Remove(unknownRange, urlRanges);
                    rangesLeft.AddRange(remains);
                }
                encodings.Add(CitationField.Url, urlRanges);
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 4a. split 
            if (this.NeedToSplit(citationText, encodings[CitationField.Unknown], cite.Authors, cite.Journal))
            {
                List<Range<int>> unknownRanges = new List<Range<int>>();
                foreach (Range<int> leftOverRange in encodings[CitationField.Unknown])
                {
                    string text = citationText.Substring(
                        leftOverRange.FromValue, leftOverRange.ToValue - leftOverRange.FromValue);
                    List<Range<int>> chunks = this.Split(text, new char[] {',', ':', '"'});

                    foreach (Range<int> chunk in chunks)
                    {
                        Range<int> newChunk = new Range<int>(
                            chunk.FromValue + leftOverRange.FromValue, chunk.ToValue + leftOverRange.FromValue);
                        if (!unknownRanges.Contains(newChunk))
                            unknownRanges.Add(newChunk);
                    }
                }
                encodings[CitationField.Unknown] = unknownRanges;
            }

            // 5a. authors 
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                unknownRanges.Sort();
                List<Range<int>> authorRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    if (string.IsNullOrEmpty(cite.Authors))
                    {
                        string text = citationText.Substring(
                            unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                        string numberString = string.Empty;
                        if (this.IsAuthorsField(ref text, out numberString))
                        {
                            if(!string.IsNullOrEmpty(numberString) && string.IsNullOrEmpty(cite.PubTime) && 
                                numberString.Length==4)
                            {
                                cite.PubTime = numberString;
                            }
                            
                            Range<int> authorRange = new Range<int>(unknownRange.FromValue, unknownRange.ToValue);
                            authorRanges.Add(authorRange);
                            cite.Authors = text;
                        }
                        else
                            rangesLeft.Add(unknownRange);
                    }
                    else
                        rangesLeft.Add(unknownRange);
                }
                encodings.Add(CitationField.AuthorList, authorRanges);
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 4b. split 
            if (this.NeedToSplit(citationText, encodings[CitationField.Unknown], cite.Authors, cite.Journal))
            {
                if (encodings.ContainsKey(CitationField.Unknown) &&
                    encodings[CitationField.Unknown].Count >= 1)
                {
                    List<Range<int>> unknownRanges = new List<Range<int>>();
                    foreach (Range<int> leftOverRange in encodings[CitationField.Unknown])
                    {
                        string text = citationText.Substring(
                            leftOverRange.FromValue, leftOverRange.ToValue - leftOverRange.FromValue);
                        List<Range<int>> chunks = this.Split(text);
                        int maxWordCountEachChunk = 0;
                        foreach (Range<int> chunk in chunks)
                        {
                            string chunkText = text.Substring(chunk.FromValue, chunk.ToValue - chunk.FromValue);
                            int wordCount = this.GetWordCount(chunkText);
                            if (wordCount > maxWordCountEachChunk)
                                maxWordCountEachChunk = wordCount;
                        }
                        if (maxWordCountEachChunk > 2)
                        {
                            foreach (Range<int> chunk in chunks)
                            {
                                Range<int> newChunk = new Range<int>(
                                    chunk.FromValue + leftOverRange.FromValue, chunk.ToValue + leftOverRange.FromValue);
                                if (!unknownRanges.Contains(newChunk))
                                    unknownRanges.Add(newChunk);
                            }
                        }
                        else
                            unknownRanges.Add(leftOverRange);
                    }
                    encodings[CitationField.Unknown] = unknownRanges;
                }
            }

            // 5b. look at authors again
            if (string.IsNullOrEmpty(cite.Authors))
            {
                if (encodings.ContainsKey(CitationField.Unknown))
                {
                    List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                    unknownRanges.Sort();
                    List<Range<int>> authorRanges = new List<Range<int>>();
                    List<Range<int>> rangesLeft = new List<Range<int>>();
                    foreach (Range<int> unknownRange in unknownRanges)
                    {
                        if (string.IsNullOrEmpty(cite.Authors))
                        {
                            string text = citationText.Substring(
                                unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                            string numberString = string.Empty;
                            if (this.IsAuthorsField(ref text, out numberString))
                            {
                                if (!string.IsNullOrEmpty(numberString) && string.IsNullOrEmpty(cite.PubTime) &&
                                    numberString.Length == 4)
                                {
                                    cite.PubTime = numberString;
                                }

                                Range<int> authorRange = new Range<int>(unknownRange.FromValue, unknownRange.ToValue);
                                authorRanges.Add(authorRange);
                                cite.Authors = text;
                            }
                            else
                                rangesLeft.Add(unknownRange);
                        }
                        else
                            rangesLeft.Add(unknownRange);
                    }
                    if (encodings.ContainsKey(CitationField.AuthorList))
                        encodings[CitationField.AuthorList] = authorRanges;
                    else
                        encodings.Add(CitationField.AuthorList, authorRanges);
                    encodings[CitationField.Unknown] = rangesLeft;
                }
            }

            // 6. journal name
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                int volumeIssuePagePos = citationText.Length;
                if (encodings.ContainsKey(CitationField.VolumeIssue) &&
                    encodings[CitationField.VolumeIssue].Count > 0)
                {
                    foreach (Range<int> viRange in encodings[CitationField.VolumeIssue])
                    {
                        if (viRange.ToValue > 0 && viRange.ToValue < volumeIssuePagePos)
                            volumeIssuePagePos = viRange.ToValue;
                    }
                }
                else if (encodings.ContainsKey(CitationField.Pages) &&
                    encodings[CitationField.Pages].Count > 0)
                {
                    foreach (Range<int> pageRange in encodings[CitationField.Pages])
                    {
                        if (pageRange.ToValue > 0 && pageRange.ToValue <= volumeIssuePagePos)
                            volumeIssuePagePos = pageRange.ToValue;
                    }
                }
                Range<int> bestFit = null;
                int minDistance = citationText.Length;

                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                unknownRanges.Sort();
                List<Range<int>> journalRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                            unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    string numberStr = string.Empty;
                    if (this.IsJournalNameField(ref text, out numberStr) && text.Length>=4)
                    {
                        if(string.IsNullOrEmpty(cite.Journal) && unknownRange.ToValue<=volumeIssuePagePos)
                        {
                            cite.Journal = text;
                            bestFit = unknownRange;
                            minDistance = volumeIssuePagePos - unknownRange.ToValue;
                        }
                        else if(unknownRange.ToValue<volumeIssuePagePos && 
                            volumeIssuePagePos - unknownRange.ToValue<minDistance)
                        {
                            cite.Journal = text;
                            bestFit = unknownRange;
                            minDistance = volumeIssuePagePos - unknownRange.ToValue;
                        }
                    }
                }
                if(bestFit !=null && bestFit.ToValue > bestFit.FromValue)
                {
                    journalRanges.Add(bestFit);
                    foreach(Range<int> range in unknownRanges)
                    {
                        if(range !=bestFit && !rangesLeft.Contains(range))
                            rangesLeft.Add(range);
                    }
                }
                if (encodings.ContainsKey(CitationField.JournalMeetingBookSiteRefName))
                    encodings[CitationField.JournalMeetingBookSiteRefName] = journalRanges;
                else
                    encodings.Add(CitationField.JournalMeetingBookSiteRefName, journalRanges);
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 6b. when journal is null, split author field and
            // find journals
            if(string.IsNullOrEmpty(cite.Journal) && 
                encodings.ContainsKey(CitationField.AuthorList) && 
                encodings[CitationField.AuthorList].Count>0)
            {
                List<Range<int>> authorRanges = encodings[CitationField.AuthorList];
                authorRanges.Sort();
                List<Range<int>> adjustedAuthorRanges=new List<Range<int>>();
                List<Range<int>> jRanges = new List<Range<int>>();
                foreach(Range<int> authorRange in authorRanges)
                {
                    string authorFieldText = citationText.Substring(
                        authorRange.FromValue, authorRange.ToValue - authorRange.FromValue);
                    Regex etalRegex = new Regex("\\s+et[\\s\\.]+al[\\s;:,\\.]*", RegexOptions.IgnoreCase);
                    Match etalMatch = etalRegex.Match(authorFieldText);
                    if (etalMatch.Success)
                    {
                        int authorEndPos = etalMatch.Index + etalMatch.Value.Length;
                        if(authorEndPos < authorFieldText.Length -3)
                        {
                            string rightText = authorFieldText.Substring(authorEndPos);
                            string numStr = string.Empty;
                            if(this.IsJournalNameField(ref rightText, out numStr))
                            {
                                cite.Journal = rightText;
                                cite.Authors = authorFieldText.Substring(0, authorEndPos);
                                Range<int> jRange=new Range<int>(authorRange.FromValue+authorEndPos, authorRange.ToValue);
                                jRanges.Add(jRange);
                                if (encodings.ContainsKey(CitationField.JournalMeetingBookSiteRefName))
                                    encodings[CitationField.JournalMeetingBookSiteRefName] = jRanges;
                                else
                                    encodings.Add(CitationField.JournalMeetingBookSiteRefName, jRanges);

                                Range<int> newAuthorRange=new Range<int>(authorRange.FromValue, authorRange.FromValue+authorEndPos);
                                adjustedAuthorRanges.Add(newAuthorRange);
                                break;
                            }
                        }
                    }
                }
                if (string.IsNullOrEmpty(cite.Journal) && authorRanges.Count > 0)
                {
                    Range<int> jRange = null;
                    
                    for (int i = 0; i < authorRanges.Count; i++)
                    {
                        Range<int> authorRange = authorRanges[i];
                        string authorText = citationText.Substring(
                            authorRange.FromValue, authorRange.ToValue - authorRange.FromValue);
                        string numStr = string.Empty;
                        if(this.IsJournalNameField(ref authorText, out numStr))
                        {
                            jRange = authorRange;
                            cite.Journal = authorText;
                            
                        }
                    }
                    if(jRange !=null)
                    {
                        jRanges.Add(jRange);
                        if (encodings.ContainsKey(CitationField.JournalMeetingBookSiteRefName))
                            encodings[CitationField.JournalMeetingBookSiteRefName] = jRanges;
                        else
                            encodings.Add(CitationField.JournalMeetingBookSiteRefName, jRanges);

                        foreach(Range<int> authorRange in authorRanges)
                        {
                            if(authorRange!=jRange)
                                adjustedAuthorRanges.Add(authorRange);
                        }
                        encodings[CitationField.AuthorList] = authorRanges;

                        if (!string.IsNullOrEmpty(cite.Journal))
                        {
                            if (cite.Authors == cite.Journal)
                                cite.Authors = null;
                            else if (cite.Authors.Contains(cite.Journal))
                                cite.Authors = cite.Authors.Replace(cite.Journal, "");
                        }
                    }
                }
            }

            // 7. title 
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                unknownRanges.Sort();
                List<Range<int>> titleRanges = new List<Range<int>>();
                List<Range<int>> rangesLeft = new List<Range<int>>();
                Range<int> titleRange = null;
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                             unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    if (this.IsTitle(text))
                    {
                        if (string.IsNullOrEmpty(cite.Title))
                        {
                            cite.Title = text;
                            titleRange = unknownRange;
                        }
                        else if (cite.Title.Length < text.Length)
                        {
                            cite.Title = text;
                            titleRange = unknownRange;
                        }
                    }
                }
                foreach (Range<int> uRange in unknownRanges)
                {
                    if (titleRange != uRange && !rangesLeft.Contains(uRange))
                        rangesLeft.Add(uRange);
                }
                titleRanges.Add(titleRange);
                encodings.Add(CitationField.Title, titleRanges);
                encodings[CitationField.Unknown] = rangesLeft;
            }

            // 8. merge journal name
            if (encodings.ContainsKey(CitationField.JournalMeetingBookSiteRefName) &&
                encodings[CitationField.JournalMeetingBookSiteRefName].Count > 0 &&
                encodings.ContainsKey(CitationField.Unknown) &&
                encodings[CitationField.Unknown].Count > 0)
            {
                List<Range<int>> journalRanges = encodings[CitationField.JournalMeetingBookSiteRefName];
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                journalRanges = this.ExpandRanges(journalRanges, unknownRanges);
                int viPosition = citationText.Length;
                if(encodings.ContainsKey(CitationField.VolumeIssue) &&
                    encodings[CitationField.VolumeIssue].Count>0)
                {
                    foreach (Range<int> vRange in encodings[CitationField.VolumeIssue])
                        if (vRange.FromValue > 0 && vRange.FromValue < viPosition)
                            viPosition = vRange.FromValue;
                }
                foreach (Range<int> journalRange in journalRanges)
                {
                    string journalText = citationText.Substring(
                        journalRange.FromValue, journalRange.ToValue - journalRange.FromValue);
                    if (string.IsNullOrEmpty(cite.Journal))
                    {
                        cite.Journal = journalText;
                    }
                    else if(journalRange.ToValue <= viPosition)
                    {
                        if(journalText.Length>cite.Journal.Length)
                            cite.Journal = journalText;
                    }
                }
            }

            // 9. number -> page, volume, issue
            if (encodings.ContainsKey(CitationField.Unknown))
            {
                int journalNamePos = citationText.Length;
                if(encodings.ContainsKey(CitationField.JournalMeetingBookSiteRefName) && 
                    encodings[CitationField.JournalMeetingBookSiteRefName].Count>0)
                {
                    foreach(Range<int> jRange in encodings[CitationField.JournalMeetingBookSiteRefName])
                    {
                        if (jRange.ToValue>0 && jRange.ToValue < journalNamePos)
                            journalNamePos = jRange.ToValue;
                    }
                }
                List<Range<int>> unknownRanges = encodings[CitationField.Unknown];
                List<Range<int>> numberRanges = new List<Range<int>>();
                foreach (Range<int> unknownRange in unknownRanges)
                {
                    string text = citationText.Substring(
                        unknownRange.FromValue, unknownRange.ToValue - unknownRange.FromValue);
                    if (this.TryParseNumbers(text, out numbers))
                    {
                        foreach (Range<int> range in numbers.Keys)
                        {
                            Range<int> numberRange = new Range<int>(
                                range.FromValue + unknownRange.FromValue, range.ToValue + unknownRange.FromValue);
                            numberRanges.Add(numberRange);
                            int number = numbers[range];
                            if (string.IsNullOrEmpty(cite.Volume) && range.FromValue>journalNamePos)
                                cite.Volume = number.ToString();
                            else if (string.IsNullOrEmpty(cite.Issue) && range.FromValue > journalNamePos)
                                cite.Issue = number.ToString();
                            else if (string.IsNullOrEmpty(cite.Pages) && range.FromValue > journalNamePos)
                                cite.Pages = number.ToString();
                        }
                    }
                }
            }

            return cite;
        }


        #region parsing
        private bool TryParseYear(string citationText, out Dictionary<Range<int>, int> years)
        {
            years=new Dictionary<Range<int>, int>();
            Regex yearRegex=new Regex(this._PubYearPattern);
            Match yearMatch = yearRegex.Match(citationText);
            if(!yearMatch.Success)
                return false;
            while(yearMatch.Success)
            {
                int year = int.Parse(yearMatch.Value);
                Range<int> matchRange=new Range<int>();
                matchRange.FromValue = yearMatch.Index;
                matchRange.ToValue = yearMatch.Index + yearMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                years.Add(matchRange, year);
                yearMatch = yearMatch.NextMatch();
            }
            return true;
        }

        private bool TryParseVolumeIssuePage(string citationText, out Dictionary<Range<int>, string> vips)
        {
            vips = new Dictionary<Range<int>, string>();
            Regex vipRegex = new Regex(this._VolumeIssuePagePattern);
            Match vipMatch = vipRegex.Match(citationText);
            while (vipMatch.Success)
            {
                string vip = vipMatch.Value;
                Range<int> matchRange = new Range<int>();
                matchRange.FromValue = vipMatch.Index;
                matchRange.ToValue = vipMatch.Index + vipMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                vips.Add(matchRange, vip);
                vipMatch = vipMatch.NextMatch();
            }
            if (vips.Count > 0)
                return true;
            else
                return false;
        }

        private bool TryParsePageRanges(string citationText, out Dictionary<Range<int>, Range<int>> pageNums)
        {
            pageNums = new Dictionary<Range<int>, Range<int>>();
            Regex pageRegex = new Regex(this._PageRangePattern);
            Regex numRegex=new Regex("\\d+");
            Regex pageKeyRegex = new Regex(this._PageKeyPattern, RegexOptions.IgnoreCase);
            Match pageKeyMatch = pageKeyRegex.Match(citationText);
            if (pageKeyMatch.Success)
            {
                while (pageKeyMatch.Success)
                {
                    string pageRangeText = pageKeyMatch.Value;
                    Match pageMatch = pageRegex.Match(pageRangeText);
                    if (pageMatch.Success)
                    {
                        Range<int> pageRange = new Range<int>();
                        pageRange.FromValue = int.Parse(pageMatch.Groups[1].Value);
                        pageRange.ToValue = int.Parse(pageMatch.Groups[2].Value);
                        Range<int> matchRange = new Range<int>();
                        matchRange.FromValue = pageKeyMatch.Index;
                        matchRange.ToValue = pageKeyMatch.Index + pageKeyMatch.Value.Length;
                        matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                        pageNums.Add(matchRange,pageRange);
                    }
                    else if(numRegex.IsMatch(pageRangeText))
                    {
                        Match numMatch = numRegex.Match(pageRangeText);
                        Range<int> pageRange = new Range<int>();
                        pageRange.FromValue = int.Parse(numMatch.Value);
                        pageRange.ToValue = int.Parse(numMatch.Value);
                        Range<int> matchRange = new Range<int>();
                        matchRange.FromValue = pageKeyMatch.Index;
                        matchRange.ToValue = pageKeyMatch.Index + pageKeyMatch.Value.Length;
                        matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                        pageNums.Add(matchRange, pageRange);
                    }

                    pageKeyMatch = pageKeyMatch.NextMatch();
                }
            }
            else
            {
                Match pageMatch = pageRegex.Match(citationText);
                while(pageMatch.Success)
                {
                    Range<int> pageRange = new Range<int>();
                    pageRange.FromValue = int.Parse(pageMatch.Groups[1].Value);
                    pageRange.ToValue = int.Parse(pageMatch.Groups[2].Value);
                    Range<int> matchRange = new Range<int>();
                    matchRange.FromValue = pageMatch.Index;
                    matchRange.ToValue = pageMatch.Index + pageMatch.Value.Length;
                    pageNums.Add(matchRange, pageRange);

                    pageMatch = pageMatch.NextMatch();
                }
            }
            if(pageNums.Count>0)
                return true;
            else
                return false;
        }

        private bool TryParseVolumeIssues(string citationText, out Dictionary<Range<int>, string> volumeIssues)
        {
            volumeIssues=new Dictionary<Range<int>, string>();
            Regex viRegex=new Regex(this._VolumeIssuePattern);
            Match viMatch = viRegex.Match(citationText);
            while(viMatch.Success)
            {
                string vi = viMatch.Value;
                Range<int> matchRange=new Range<int>();
                matchRange.FromValue = viMatch.Index;
                matchRange.ToValue = viMatch.Index + viMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                volumeIssues.Add(matchRange, vi);
                viMatch = viMatch.NextMatch();
            }
            if(volumeIssues.Count>0)
                return true;
            else
                return false;
        }

        private bool TryParseVolume(string citationText, out Dictionary<Range<int>, int> volumes)
        {
            volumes=new Dictionary<Range<int>, int>();
            Regex vRegex = new Regex(this._VolumePattern, RegexOptions.IgnoreCase);
            Match vMatch = vRegex.Match(citationText);
            while (vMatch.Success)
            {
                int v = int.Parse(vMatch.Groups["v"].Value);
                Range<int> matchRange = new Range<int>();
                matchRange.FromValue = vMatch.Index;
                matchRange.ToValue = vMatch.Index + vMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                volumes.Add(matchRange, v);
                vMatch = vMatch.NextMatch();
            }
            if (volumes.Count > 0)
                return true;
            else
                return false;
        }

        private bool TryParseIssue(string citationText, out Dictionary<Range<int>, int> issues)
        {
            issues=new Dictionary<Range<int>, int>();
            Regex iRegex = new Regex(this._IssuePattern, RegexOptions.IgnoreCase);
            Match iMatch = iRegex.Match(citationText);
            while (iMatch.Success)
            {
                int i = int.Parse(iMatch.Groups["i"].Value);
                Range<int> matchRange = new Range<int>();
                matchRange.FromValue = iMatch.Index;
                matchRange.ToValue = iMatch.Index + iMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                issues.Add(matchRange, i);
                iMatch = iMatch.NextMatch();
            }
            if (issues.Count > 0)
                return true;
            else
                return false;
        }

        private bool TryParseNumbers(string citationText, out Dictionary<Range<int>, int> numbers)
        {
            numbers=new Dictionary<Range<int>, int>();
            Regex numRegex = new Regex("\\b(\\d+)\\b");
            List<char> stopChars=new List<char>();
            foreach(char k in this._StopWordsEncodings.Keys)
            {
                foreach(char s in this._StopWordsEncodings[k])
                {
                    stopChars.Add(s);
                }
            }
            Match numMatch = numRegex.Match(citationText);
            while (numMatch.Success)
            {
                int number = int.Parse(numMatch.Groups[1].Value);
                Range<int> matchRange = new Range<int>();
                matchRange.FromValue = numMatch.Index;
                matchRange.ToValue = numMatch.Index + numMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                if (!numbers.ContainsKey(matchRange))
                {
                    numbers.Add(matchRange, number);
                }
                numMatch = numMatch.NextMatch();
            }
            
            if(numbers.Count>0)
                return true;
            else
                return false;
        }

        private bool TryParseDates(string citationText, out Dictionary<Range<int>, string> dates)
        {
            dates = new Dictionary<Range<int>, string>();
            Regex dateRegex=new Regex(this._DatePattern, RegexOptions.IgnoreCase);
            Match dateMatch = dateRegex.Match(citationText);
            while(dateMatch.Success)
            {
                string dateVal = dateMatch.Value;
                Range<int> matchRange=new Range<int>();
                matchRange.FromValue = dateMatch.Index;
                matchRange.ToValue = dateMatch.Index + dateMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                dates.Add(matchRange, dateVal);
                dateMatch = dateMatch.NextMatch();
            }

            if(dates.Count>0)
                return true;
            else
                return false;
        }

        private bool TryParseUrls(string citationText, out Dictionary<Range<int>, string> urls)
        {
            urls = new Dictionary<Range<int>, string>();
            Regex urlRegex=new Regex(this._UrlPattern, RegexOptions.IgnoreCase);
            Match urlMatch = urlRegex.Match(citationText);
            while(urlMatch.Success)
            {
                string url = urlMatch.Value;
                Range<int> matchRange = new Range<int>();
                matchRange.FromValue = urlMatch.Index;
                matchRange.ToValue = urlMatch.Index + urlMatch.Value.Length;
                matchRange.ToValue = this.EncloseStopChars(citationText, matchRange.ToValue);
                urls.Add(matchRange, url);
                urlMatch = urlMatch.NextMatch();
            }
            if(urls.Count>0)
                return true;
            else
                return false;
        }

        private int EncloseStopChars(string citationText, int endPosition)
        {
            char[] stopChars = new char[] {'.',' ', ',', ';', ':', '?', '!','"'};
            List<char> stopCharList=new List<char>();
            stopCharList.AddRange(stopChars);
            while (endPosition < citationText.Length - 1)
            {
                char c = citationText[endPosition + 1];
                if(stopCharList.Contains(c))
                {
                    endPosition += 1;
                }
                else
                {
                    break;
                }
            }
            return endPosition;
        }
        #endregion

        #region validate 
        private bool IsAuthorsField(ref string regionText, out string numberStr)
        {
            numberStr = string.Empty;
            
            
            // remove number prefix and suffix
            Regex prefixNumber = new Regex("^(\\d+).+$");
            if (prefixNumber.IsMatch(regionText))
            {
                numberStr = prefixNumber.Match(regionText).Groups[1].Value;
                regionText = prefixNumber.Replace(regionText, "");
            }
            Regex suffixNumber = new Regex("^.+[^\\d](\\d+)$");
            if (suffixNumber.IsMatch(regionText))
            {
                numberStr = suffixNumber.Match(regionText).Groups[1].Value;
                regionText = suffixNumber.Replace(regionText, "");
            }
            regionText.Trim();
            regionText = regionText.Replace(";", ",");
            regionText = regionText.Replace(":", ",");
            regionText = regionText.Replace("&", ",");

            Regex authorRegex=new Regex(this._AuthorListPattern);
            if (authorRegex.IsMatch(regionText))
            {
                List<char> stopChars = new List<char>();
                foreach (char k in this._StopWordsEncodings.Keys)
                {
                    stopChars.AddRange(this._StopWordsEncodings[k]);
                }
                string[] names = regionText.Split(stopChars.ToArray());
                foreach (string name in names)
                {
                    string[] words = name.Split(new char[] { ' ' });
                    if (words.Length > 5)
                        return false;
                }
                return true;
            }
            else
                return false;
        }

        private bool IsJournalNameField(ref string regionText, out string numberStr)
        {
            numberStr = string.Empty;

            Regex etalRegex = new Regex("\\s+et[\\s\\.]+al[\\s;:,\\.]*", RegexOptions.IgnoreCase);
            if (etalRegex.IsMatch(regionText))
                return false;

            // remove number prefix and suffix
            while (regionText.EndsWith(",") || 
                regionText.EndsWith(";") ||
                regionText.EndsWith(":"))
                regionText = regionText.Substring(0, regionText.Length - 1) + ".";
            while (regionText.StartsWith(",") ||
                regionText.StartsWith(";") ||
                regionText.StartsWith(":"))
                regionText = "." + regionText.Substring(1);
            
            Regex prefixNumber = new Regex("^(\\d+).+$");
            if (prefixNumber.IsMatch(regionText))
            {
                numberStr = prefixNumber.Match(regionText).Groups[1].Value;
                regionText = regionText.Replace(numberStr, "");
            }
            Regex suffixNumber = new Regex("^.+[^\\d](\\d+)$");
            if(suffixNumber.IsMatch(regionText))
            {
                numberStr = suffixNumber.Match(regionText).Groups[1].Value;
                regionText = regionText.Replace(numberStr, "");
            }
            while (regionText.EndsWith(",") || 
                regionText.EndsWith(";") ||
                regionText.EndsWith(":"))
                regionText = regionText.Substring(0, regionText.Length - 1) + ".";
            while (regionText.StartsWith(",") ||
                regionText.StartsWith(";") ||
                regionText.StartsWith(":"))
                regionText = "." + regionText.Substring(1);
            regionText.Trim();
            while (regionText.EndsWith(",") || 
                regionText.EndsWith(";") ||
                regionText.EndsWith(":"))
                regionText = regionText.Substring(0, regionText.Length - 1) + ".";
            while (regionText.StartsWith(",") ||
                regionText.StartsWith(";") ||
                regionText.StartsWith(":"))
                regionText = "." + regionText.Substring(1);

            Regex journalRegex = new Regex(this._JournalNamePattern);
            if (journalRegex.IsMatch(regionText))
                return true;
            else
                return false;
        }

        private bool IsTitle(string titleText)
        {
            if (this.GetWordCount(titleText) >= 2 && titleText.Trim().Length > 3)
                return true;
            else
                return false;
        }
        #endregion

        #region splits
        private int GetWordCount(string text)
        {
            int wordCount = 0;
            Regex wordRegex = new Regex("[a-zA-Z]+");
            string[] words = text.Split(new char[] {' '});
            foreach(string word in words)
            {
                if (wordRegex.IsMatch(word))
                    wordCount++;
            }
            return wordCount;
        }

        private List<Range<int>> Remove(Range<int> fromRange, List<Range<int>> ranges)
        {
            List<Range<int>> remains = new List<Range<int>>();
            remains.Add(fromRange);
            foreach (Range<int> range in ranges)
            {
                List<Range<int>> toRemoves = new List<Range<int>>();
                List<Range<int>> toAdds = new List<Range<int>>();
                foreach (Range<int> remain in remains)
                {
                    if (remain.OverlapWith(range))
                    {
                        if (remain.FromValue.CompareTo(range.FromValue) <= 0
                            && remain.ToValue.CompareTo(range.ToValue) >= 0)
                        {
                            if (!toRemoves.Contains(remain))
                                toRemoves.Add(remain);
                            Range<int> range1 = new Range<int>(remain.FromValue, range.FromValue - 1);
                            if (range1.ToValue > range1.FromValue)
                                toAdds.Add(range1);
                            Range<int> range2 = new Range<int>(range.ToValue + 1, remain.ToValue);
                            if(range2.ToValue>range2.FromValue)
                                toAdds.Add(range2);
                        }
                        else if (remain.FromValue.CompareTo(range.FromValue) >= 0 &&
                                 remain.ToValue.CompareTo(range.ToValue) <= 0)
                        {
                            if (!toRemoves.Contains(remain))
                                toRemoves.Add(remain);
                        }
                        else if (remain.FromValue.CompareTo(range.FromValue) <= 0 &&
                                 remain.ToValue.CompareTo(range.ToValue) <= 0)
                        {
                            if (!toRemoves.Contains(remain))
                                toRemoves.Add(remain);
                            Range<int> range1 = new Range<int>(remain.FromValue, range.FromValue - 1);
                            if(range1.ToValue>range1.FromValue)
                                toAdds.Add(range1);
                        }
                        else if (remain.FromValue.CompareTo(range.FromValue) <= 0 &&
                                 remain.ToValue.CompareTo(range.ToValue) <= 0)
                        {
                            if (!toRemoves.Contains(remain))
                                toRemoves.Add(remain);
                            Range<int> range1 = new Range<int>(range.ToValue + 1, remain.ToValue);
                            if(range1.ToValue>range1.FromValue)
                                toAdds.Add(range1);
                        }
                        break;
                    }
                }

                if (toRemoves.Count > 0)
                {
                    foreach (Range<int> toRemove in toRemoves)
                        remains.Remove(toRemove);
                }
                if (toAdds.Count > 0)
                {
                    foreach (Range<int> toAdd in toAdds)
                        remains.Add(toAdd);
                }
            }

            return remains;
        }

        private bool NeedToSplit(string citationText, List<Range<int>> unknownRegions,
            string authors, string journals)
        {
            int required = 2;
            if (!string.IsNullOrEmpty(authors))
                required--;
            if (!string.IsNullOrEmpty(journals))
                required--;
            int matches = 0;
            string numStr;
            foreach(Range<int> range in unknownRegions)
            {
                string rangeText = citationText.Substring(range.FromValue, range.ToValue - range.FromValue);
                if(this.IsAuthorsField(ref rangeText, out numStr))
                {
                    matches++;
                }
                else if(this.IsJournalNameField(ref rangeText, out numStr))
                {
                    matches++;
                }
            }
            if (matches >= required)
                return false;
            else
                return true;
        }

        private List<Range<int>> Split(string text)
        {
            List<Range<int>> ranges = new List<Range<int>>();
            Dictionary<int, Range<int>> chunks = SentenceBoundaryUtil.GetCitationPhraseRanges(text);
            if (chunks != null && chunks.Count > 0)
                ranges.AddRange(chunks.Values);
            return ranges;
            //char[] splitters = new char[] { '.' };
            
            //string[] regions = text.Split(splitters);
            //int startPos = 0;
            //foreach (string region in regions)
            //{
            //    int endPos = startPos + region.Length;
            //    endPos = this.EncloseStopChars(text, endPos);
            //    Range<int> range = new Range<int>(startPos, endPos);
            //    ranges.Add(range);
            //    startPos = endPos;
            //}
            //return ranges;
        }

        private List<Range<int>> Split(string text, char[] splitters)
        {
            List<Range<int>> ranges = new List<Range<int>>();
            //Dictionary<int, Range<int>> chunks = SentenceBoundaryUtil.GetCitationPhraseRanges(text);
            //if (chunks != null && chunks.Count > 0)
            //    ranges.AddRange(chunks.Values);
            //return ranges;
            string[] regions = text.Split(splitters);
            int startPos = 0;
            foreach (string region in regions)
            {
                if (region.Length == 0)
                    startPos += 1;
                else
                {
                    int endPos = startPos + region.Length;
                    if (endPos > text.Length)
                        endPos = text.Length;
                    Range<int> range = new Range<int>(startPos, endPos);
                    ranges.Add(range);
                    startPos = endPos + 1;
                }
            }
            return ranges;
        }

        private List<Range<int>> ExpandRanges(List<Range<int>> sourceRanges, List<Range<int>> islands)
        {
            List<Range<int>> rangesJoined=new List<Range<int>>();

            islands.Sort();
            List<Range<int>> newIslands=new List<Range<int>>();
            int j=0;
            while(j<islands.Count)
            {
                Range<int> current = islands[j];
                int k = j + 1;
                while(k<islands.Count)
                {
                    Range<int> next = islands[k];
                    if (current.OverlapWith(next) ||
                        current.FromValue == next.ToValue + 1 ||
                        current.ToValue == next.FromValue - 1)
                    {
                        current = current.Union(next);
                        islands.Remove(next);
                    }
                    else
                        k++;
                }
                newIslands.Add(current);
                j++;
            }
            islands = newIslands;

            foreach(Range<int> srcRange in sourceRanges)
            {
                Range<int> newRange = new Range<int>(srcRange.FromValue,srcRange.ToValue);
                foreach(Range<int> island in islands)
                {
                    if (newRange.OverlapWith(island) ||
                        newRange.ToValue == island.FromValue - 1 || 
                        newRange.FromValue == island.ToValue + 1)
                        newRange = newRange.Union(island);
                }
                int i = 0;
                while(i<rangesJoined.Count)
                {
                    Range<int> existingRange = rangesJoined[i];
                    if (existingRange.OverlapWith(newRange) || 
                        existingRange.ToValue == newRange.FromValue -1 || 
                        existingRange.FromValue == newRange.ToValue +1)
                    {
                        newRange = newRange.Union(existingRange);
                        rangesJoined.Remove(existingRange);
                    }
                    else
                        i++;
                }
                rangesJoined.Add(newRange);
            }
            return rangesJoined;
        }
        #endregion
    }
}
